Merge pull request #711 from dsander/pushbullet-unification

Pushbullet pull request unification

Dominik Sander 9 years ago
parent
commit
da77a8a333

+ 85 - 13
app/models/agents/pushbullet_agent.rb

@@ -1,22 +1,36 @@
1 1
 module Agents
2 2
   class PushbulletAgent < Agent
3
+    include FormConfigurable
4
+
3 5
     cannot_be_scheduled!
4 6
     cannot_create_events!
5 7
 
8
+    before_validation :create_device, on: :create
9
+
10
+    API_BASE = 'https://api.pushbullet.com/v2/'
11
+    TYPE_TO_ATTRIBUTES = {
12
+            'note'    => [:title, :body],
13
+            'link'    => [:title, :body, :url],
14
+            'address' => [:name, :address]
15
+    }
16
+    class Unauthorized < StandardError; end
17
+
6 18
     description <<-MD
7 19
       The Pushbullet agent sends pushes to a pushbullet device
8 20
 
9
-      To authenticate you need to set the `api_key`, you can find yours at your account page:
21
+      To authenticate you need to either the `api_key` or create a `pushbullet_api_key` credential, you can find yours at your account page:
10 22
 
11 23
       `https://www.pushbullet.com/account`
12 24
 
13
-      Currently you need to get a the device identification manually:
25
+      If you do not select an existing device, Huginn will create a new one with the name 'Huginn'.
14 26
 
15
-      `curl -u <your api key here>: https://api.pushbullet.com/api/devices`
27
+      You have to provide a message `type` which has to be `note`, `link`, or `address`. The message types `checklist`, and `file` are not supported at the moment.
16 28
 
17
-      Put one of the retured `iden` strings into the `device_id` field.
29
+      Depending on the message `type` you can use additional fields:
18 30
 
19
-      You can provide a `title` and a `body`.
31
+      * note: `title` and `body`
32
+      * link: `title`, `body`, and `url`
33
+      * address: `name`, and `address`
20 34
 
21 35
       In every value of the options hash you can use the liquid templating, learn more about it at the [Wiki](https://github.com/cantino/huginn/wiki/Formatting-Events-using-Liquid).
22 36
     MD
@@ -25,14 +39,39 @@ module Agents
25 39
       {
26 40
         'api_key' => '',
27 41
         'device_id' => '',
28
-        'title' => "Hello from Huginn!",
42
+        'title' => "{{title}}",
29 43
         'body' => '{{body}}',
44
+        'type' => 'note',
30 45
       }
31 46
     end
32 47
 
48
+    form_configurable :api_key, roles: :validatable
49
+    form_configurable :device_id, roles: :completable
50
+    form_configurable :type, type: :array, values: ['note', 'link', 'address']
51
+    form_configurable :title
52
+    form_configurable :body, type: :text
53
+    form_configurable :url
54
+    form_configurable :name
55
+    form_configurable :address
56
+
33 57
     def validate_options
34
-      errors.add(:base, "you need to specify a pushbullet api_key") unless options['api_key'].present?
58
+      errors.add(:base, "you need to specify a pushbullet api_key") if options['api_key'].blank?
35 59
       errors.add(:base, "you need to specify a device_id") if options['device_id'].blank?
60
+      errors.add(:base, "you need to specify a valid message type") if options['type'].blank? or not ['note', 'link', 'address'].include?(options['type'])
61
+      TYPE_TO_ATTRIBUTES[options['type']].each do |attr|
62
+        errors.add(:base, "you need to specify '#{attr.to_s}' for the type '#{options['type']}'") if options[attr].blank?
63
+      end
64
+    end
65
+
66
+    def validate_api_key
67
+      devices
68
+      true
69
+    rescue Unauthorized
70
+      false
71
+    end
72
+
73
+    def complete_device_id
74
+      devices.map { |d| {text: d['nickname'], id: d['iden']} }
36 75
     end
37 76
 
38 77
     def working?
@@ -41,19 +80,52 @@ module Agents
41 80
 
42 81
     def receive(incoming_events)
43 82
       incoming_events.each do |event|
44
-        response = HTTParty.post "https://api.pushbullet.com/api/pushes", query_options(event)
45
-        error(response.body) if response.body.include? 'error'
83
+        safely do
84
+          response = request(:post, 'pushes', query_options(event))
85
+        end
46 86
       end
47 87
     end
48 88
 
49 89
     private
90
+    def safely
91
+      yield
92
+    rescue Unauthorized => e
93
+      error(e.message)
94
+    end
95
+
96
+    def request(http_method, method, options)
97
+      response = JSON.parse(HTTParty.send(http_method, API_BASE + method, options).body)
98
+      raise Unauthorized, response['error']['message'] if response['error'].present?
99
+      response
100
+    end
101
+
102
+    def devices
103
+      response = request(:get, 'devices', basic_auth)
104
+      response['devices'].select { |d| d['pushable'] == true }
105
+    rescue Unauthorized
106
+      []
107
+    end
108
+
109
+    def create_device
110
+      return if options['device_id'].present?
111
+      safely do
112
+        response = request(:post, 'devices', basic_auth.merge(body: {nickname: 'Huginn', type: 'stream'}))
113
+        self.options[:device_id] = response['iden']
114
+      end
115
+    end
116
+
117
+
118
+    def basic_auth
119
+      {basic_auth: {username: interpolated[:api_key].presence || credential('pushbullet_api_key'), password: ''}}
120
+    end
50 121
 
51 122
     def query_options(event)
52 123
       mo = interpolated(event)
53
-      {
54
-        :basic_auth => {:username => mo[:api_key], :password => ''},
55
-        :body => {:device_iden => mo[:device_id], :title => mo[:title], :body => mo[:body], :type => 'note'}
56
-      }
124
+      basic_auth.merge(body: {device_iden: mo[:device_id], type: mo[:type]}.merge(payload(mo)))
125
+    end
126
+
127
+    def payload(mo)
128
+      Hash[TYPE_TO_ATTRIBUTES[mo[:type]].map { |k| [k, mo[k]] }]
57 129
     end
58 130
   end
59 131
 end

+ 19 - 0
db/migrate/20150219213604_add_type_option_attribute_to_pushbullet_agents.rb

@@ -0,0 +1,19 @@
1
+class AddTypeOptionAttributeToPushbulletAgents < ActiveRecord::Migration
2
+  def up
3
+    Agents::PushbulletAgent.find_each do |agent|
4
+      if agent.options['type'].nil?
5
+        agent.options['type'] = 'note'
6
+        agent.save!
7
+      end
8
+    end
9
+  end
10
+
11
+  def down
12
+    Agents::PushbulletAgent.find_each do |agent|
13
+      if agent.options['type'].present?
14
+        agent.options.delete 'type'
15
+        agent.save(validate: false)
16
+      end
17
+    end
18
+  end
19
+end

+ 93 - 16
spec/models/agents/pushbullet_agent_spec.rb

@@ -3,10 +3,14 @@ require 'spec_helper'
3 3
 describe Agents::PushbulletAgent do
4 4
   before(:each) do
5 5
     @valid_params = {
6
-                      'api_key' => 'token',
6
+                      'api_key'   => 'token',
7 7
                       'device_id' => '124',
8
-                      'body' => '{{body}}',
9
-                      'title' => 'hello from huginn'
8
+                      'body'      => '{{body}}',
9
+                      'url'       => 'url',
10
+                      'name'      => 'name',
11
+                      'address'   => 'address',
12
+                      'title'     => 'hello from huginn',
13
+                      'type'      => 'note'
10 14
                     }
11 15
 
12 16
     @checker = Agents::PushbulletAgent.new(:name => "somename", :options => @valid_params)
@@ -29,34 +33,83 @@ describe Agents::PushbulletAgent do
29 33
       expect(@checker).not_to be_valid
30 34
     end
31 35
 
32
-    it "should require the device_id" do
36
+    it "should try do create a device_id" do
33 37
       @checker.options['device_id'] = nil
34 38
       expect(@checker).not_to be_valid
35 39
     end
40
+
41
+    it "should require fields based on the type" do
42
+      @checker.options['type'] = 'address'
43
+      @checker.options['address'] = nil
44
+      expect(@checker).not_to be_valid
45
+    end
36 46
   end
37 47
 
38 48
   describe "helpers" do
39
-    it "should return the query_options" do
40
-      expect(@checker.send(:query_options, @event)).to eq({
41
-        :body => {:title => 'hello from huginn', :body => 'One two test', :device_iden => @checker.options[:device_id], :type => 'note'},
42
-        :basic_auth => {:username =>@checker.options[:api_key], :password=>''}
43
-      })
49
+    before(:each) do
50
+      @base_options = {
51
+        body: { device_iden: @checker.options[:device_id] },
52
+        basic_auth: { username: @checker.options[:api_key], :password=>'' }
53
+      }
54
+    end
55
+    context "#query_options" do
56
+      it "should work for a note" do
57
+        options = @base_options.deep_merge({
58
+          body: {title: 'hello from huginn', body: 'One two test', type: 'note'}
59
+        })
60
+        expect(@checker.send(:query_options, @event)).to eq(options)
61
+      end
62
+
63
+      it "should work for a link" do
64
+        @checker.options['type'] = 'link'
65
+        options = @base_options.deep_merge({
66
+          body: {title: 'hello from huginn', body: 'One two test', type: 'link', url: 'url'}
67
+        })
68
+        expect(@checker.send(:query_options, @event)).to eq(options)
69
+      end
70
+
71
+      it "should work for an address" do
72
+        @checker.options['type'] = 'address'
73
+        options = @base_options.deep_merge({
74
+          body: {name: 'name', address: 'address', type: 'address'}
75
+        })
76
+        expect(@checker.send(:query_options, @event)).to eq(options)
77
+      end
78
+    end
79
+  end
80
+
81
+  describe '#validate_api_key' do
82
+    it "should return true when working" do
83
+      mock(@checker).devices
84
+      expect(@checker.validate_api_key).to be_truthy
85
+    end
86
+
87
+    it "should return true when working" do
88
+      mock(@checker).devices { raise Agents::PushbulletAgent::Unauthorized }
89
+      expect(@checker.validate_api_key).to be_falsy
90
+    end
91
+  end
92
+
93
+  describe '#complete_device_id' do
94
+    it "should return an array" do
95
+      mock(@checker).devices { [{'iden' => '12345', 'nickname' => 'huginn'}] }
96
+      expect(@checker.complete_device_id).to eq([{:text=>"huginn", :id=>"12345"}])
44 97
     end
45 98
   end
46 99
 
47 100
   describe "#receive" do
48
-    it "send a message to the hipchat" do
49
-      stub_request(:post, "https://token:@api.pushbullet.com/api/pushes").
50
-        with(:body => "device_iden=124&title=hello%20from%20huginn&body=One%20two%20test&type=note").
51
-        to_return(:status => 200, :body => "ok", :headers => {})
101
+    it "send a note" do
102
+      stub_request(:post, "https://token:@api.pushbullet.com/v2/pushes").
103
+        with(:body => "device_iden=124&type=note&title=hello%20from%20huginn&body=One%20two%20test").
104
+        to_return(:status => 200, :body => "{}", :headers => {})
52 105
       dont_allow(@checker).error
53 106
       @checker.receive([@event])
54 107
     end
55 108
 
56 109
     it "should log resquests which return an error" do
57
-      stub_request(:post, "https://token:@api.pushbullet.com/api/pushes").
58
-        with(:body => "device_iden=124&title=hello%20from%20huginn&body=One%20two%20test&type=note").
59
-        to_return(:status => 200, :body => "error", :headers => {})
110
+      stub_request(:post, "https://token:@api.pushbullet.com/v2/pushes").
111
+        with(:body => "device_iden=124&type=note&title=hello%20from%20huginn&body=One%20two%20test").
112
+        to_return(:status => 200, :body => '{"error": {"message": "error"}}', :headers => {})
60 113
       mock(@checker).error("error")
61 114
       @checker.receive([@event])
62 115
     end
@@ -69,4 +122,28 @@ describe Agents::PushbulletAgent do
69 122
       expect(@checker).to be_working
70 123
     end
71 124
   end
125
+
126
+  describe '#devices' do
127
+    it "should return an array of devices" do
128
+      stub_request(:get, "https://token:@api.pushbullet.com/v2/devices").
129
+         to_return(:status => 200, :body => '{"devices": [{"pushable": false}, {"nickname": "test", "iden": "iden", "pushable": true}]}', :headers => {})
130
+      expect(@checker.send(:devices)).to eq([{"nickname"=>"test", "iden"=>"iden", "pushable"=>true}])
131
+    end
132
+
133
+    it "should return an empty array on error" do
134
+      stub(@checker).request { raise Agents::PushbulletAgent::Unauthorized }
135
+      expect(@checker.send(:devices)).to eq([])
136
+    end
137
+  end
138
+
139
+  describe '#create_device' do
140
+    it "should create a new device and assign it to the options" do
141
+      stub_request(:post, "https://token:@api.pushbullet.com/v2/devices").
142
+         with(:body => "nickname=Huginn&type=stream").
143
+         to_return(:status => 200, :body => '{"iden": "udm0Tdjz5A7bL4NM"}', :headers => {})
144
+      @checker.options['device_id'] = nil
145
+      @checker.send(:create_device)
146
+      expect(@checker.options[:device_id]).to eq('udm0Tdjz5A7bL4NM')
147
+    end
148
+  end
72 149
 end